Skip to main content

06 · Deep Research:让一群 Agent 帮你写一份带引用的报告

零、开始之前

这篇的目标:搞清楚 Deep Research 这套东西是怎么拼出来的,能自己设计一个类似的系统,并且知道每个环节在防什么。

需要的前置知识前五篇都要读过。这一篇没有新范式,它是前五个的组合。

读完你会明白

  • Deep Research 比「多智能体 + 搜索」多了哪两样东西
  • Anthropic 线上的 Research 功能,主管是怎么决定「派几个人」的
  • 为什么子研究员回来之前必须先压缩
  • 为什么引用要单独交给一个 Agent 做

这一篇有一份别处没有的材料

Anthropic 把自家线上 Research 功能的提示词原文开源了,MIT 协议,就在 claude-cookbooks 里。 那是一份 23KB 的编排提示词,「派几个人、每人几次工具调用、什么时候收手」全是明文。 学这个范式,读它比读任何代码都值。

一、先看一个真实问题

你要写一份报告:

「2026 年主流 Agent 框架的选型对比,要有数据支撑,要能引用来源。」

01 ReAct:搜十几次,上下文爆了(05 第一节讲过)。

05 Supervisor:派三个子 Agent 分别查三个框架,上下文问题解决了,并行也有了。

但你会发现报告还是不能用,因为缺两样东西:

缺什么具体表现
没有引用报告里说「LangGraph 有 4 万星」,你没法验证这是查到的还是编的
信息损耗子 Agent 返回的结论太粗,写报告时发现关键细节没了,只能再派一轮

Deep Research 就是在 Supervisor 的基础上,补上这两块。

二、概念:它是前五个范式的叠加

先看整体形状:

对照前五篇看,只有黄色那两块是新的:

组件来自哪一篇
写研究简报03 Plan 的规划阶段
主管派活05 Supervisor
子研究员内部循环01 ReAct
并行02 Workflow 的并行模式
「还有缺口就再派一轮」04 Reflection
③ 压缩Deep Research 特有
⑤ 引用Deep Research 特有

所以这一篇的重点,就是搞懂这两个新环节,以及主管的派活策略。

三、拆 Anthropic 的生产提示词

三个文件,在 claude-cookbooks/patterns/agents/prompts/

文件大小角色
research_lead_agent.md23KB主管
research_subagent.md9.1KB子研究员
citations_agent.md2.9KB引用代理

3.1 主管做的第一件事:判题型

主管拿到任务后,不是马上派活,而是先判断这是哪一类问题。因为不同题型的派活方式完全不同。

题型什么样的问题怎么派
Depth-first
深度优先
一个问题,需要多个角度来看派多个子代理,各自用不同的方法论/视角攻同一个问题
Breadth-first
广度优先
能拆成互相独立的子问题每个子问题一个子代理,天然并行
Straightforward
直球
单点事实查询1 个子代理就够

原文给的例子很有辨识度:

  • 「抑郁症最有效的治疗方法是什么」→ 深度优先(多种疗法、多个学派的视角)
  • 「比较三个北欧国家的经济体制」→ 广度优先(三个国家可以独立研究)
  • 「东京现在人口多少」→ 直球

这个分类是整个编排的分叉点。 多数自建 Deep Research 系统的问题就出在这儿:不管什么问题都用同一套派活策略

3.2 派几个人:有一张明确的数字表

这是最实用的一段。Anthropic 直接给了数量指南:

复杂度子代理数例子
简单1「今年报税截止日是哪天」
标准2-3「比较三大云厂商」→ 一家一个
中等3-5「分析 AI 对医疗的影响」→ 监管/临床/经济/技术四个面
5-10(硬上限 20「财富 500 强 CEO 的出生地和年龄」→ 10 个代理各管 50 个

三条附加规则,每条都在防一种具体的浪费

① 「即使是最简单的查询,也总是至少创建 1 个子代理」

这条反直觉:查个人口数字,主管自己搜一下不就完了?

但原文的理由是「以确保正确收集来源」—— 主管自己搜的话,就没有独立的信息采集轨迹,下游的引用环节会挂不上。这是为了 ⑤ 而付的固定成本。

② 「绝不要超过 20 个子代理,除非绝对必要」

原文的措辞值得原样记住:

如果一个任务看起来需要超过 20 个子代理,通常意味着你应该重构方法……优先选择更少、更强的子代理,而不是许多过于狭窄的。更多子代理 = 更多开销。

③ 「避免子代理之间重叠」

重叠是并行编排里最贵的浪费:三个代理搜了同一批网页,你付了三份钱,拿到一份信息

3.3 派活时必须说清楚的七件事

主管给子代理的任务描述,必须包含:

  1. 具体研究目标 —— 理想情况下每个子代理只有 1 个核心目标
  2. 期望的输出格式 —— 实体列表?事实报告?还是回答某个具体问题
  3. 背景上下文 —— 用户的问题是什么,这个子代理在整体计划里的位置
  4. 要回答的关键问题
  5. 建议的起点和信源 —— 什么算高质量来源,哪些来源不可信要避开
  6. 该用哪些工具 —— 网页搜索?还是内部的 Drive / Gmail / Slack
  7. 精确的范围边界 —— 防止研究漂移

第 7 条正好对应 05 的排查表里那条「派出去干 A,回来交了 B」。

Anthropic 还在提示词里放了一个完整的正面范例,是关于半导体供应链的,足足一百多个词,具体到「去 SEC EDGAR 数据库查」「优先原始信源而非新闻聚合」「重点关注当前瓶颈和新厂产能预测」。

这个范例本身就说明了标准有多高。 你随手写的「研究一下 X」离这个差得非常远 —— 这大概是自建系统效果差的头号原因。

3.4 子研究员:预算是硬性的

子代理那份 9.1KB 的提示词,核心是给它一个明确的工具调用预算

任务难度允许的工具调用次数
简单(「今年报税截止日」)< 5
中等5
困难~10
非常困难 / 多部分最多 15

然后是硬上限:

为防止系统过载,要求你保持在 20 次工具调用约 100 个来源以内。这是绝对最大上限。如果超过这个限制,子代理将被终止。

注意这是两层保险

为什么要两层?因为只靠提示词约束,模型一定会超。它总觉得再查一条就更全了。

子代理内部跑的是显式的 OODA 循环(observe 观察 / orient 定位 / decide 决策 / act 行动),要求最少 5 次、最多 10 次工具调用。本质上就是 01 ReAct,只是把「想什么」结构化了。

3.5 主管的三条硬规则

① 收益递减就立刻停

当进一步研究已经收益递减、你已经能给出足够好的答案时,停止进一步研究,不要创建任何新子代理。

Deep Research 最大的成本黑洞就是「再查一轮说不定更全」。这条规则把「什么时候够了」显式交给主管判断。

② 绝不让子代理写最终报告

绝不创建子代理来生成最终报告 —— 你自己写,永远不允许用子代理来创建报告。

为什么?因为只有主管看过全部子代理的结果。 派个子代理去写报告,它拿到的是二手摘要,信息要损耗两次

③ 主管不做主要研究

你的主要角色是协调、指导和综合 —— 不是自己做主要研究

只有当某个关键问题子代理没覆盖到时,主管才自己动手。

②和③合起来是一条完整的分工原则

主管不查资料,但必须自己写结论。

3.6 引用为什么要单独一个 Agent

citations_agent.md 只有 2.9KB,职责单一:给已经写好的报告逐句挂来源

为什么不让主管边写边挂?因为「写得流畅」和「挂得准确」是两个互相拉扯的目标。混在一起,模型会为了行文顺畅而牺牲引用精度 —— 该标的地方不标,或者标一个大概相关的。

这和 04 Reflection 里「生成和批判要分开」是完全相同的道理:一次只让模型追求一个目标。

四、拆代码骨架:open_deep_research

提示词讲完,看代码怎么组织。拆 langchain-ai/open_deep_research(12,636★,MIT)的 deep_researcher.py,30KB,六个节点

async def clarify_with_user(...)        # ① 需要跟用户确认吗
async def write_research_brief(...) # ② 把对话压成研究简报
async def supervisor(...) # ③ 主管决策
async def supervisor_tools(...) # 主管派活的执行
async def researcher(...) # ④ 子研究员 ReAct 循环
async def researcher_tools(...)
async def compress_research(...) # ⑤ 压缩
async def final_report_generation(...) # ⑥ 写报告

4.1 三条独立的消息通道

这是状态设计里最值得学的一点:

class AgentState(MessagesState):
supervisor_messages: ... # 主管的对话
research_brief: Optional[str]
raw_notes: list[str] = [] # 原始材料
notes: list[str] = [] # 压缩后的

class ResearcherState(TypedDict):
researcher_messages: ... # 子研究员自己的对话
compressed_research: str

用户对话、主管对话、子研究员对话是三份完全独立的消息历史。

这是 05 里「上下文隔离」的彻底版本 —— 不是靠 output_mode 事后裁剪,而是从状态结构上就分开

raw_notesnotes 也是一对:前者存原始材料,后者存压缩结果。写报告时用 notes,需要溯源时查 raw_notes

4.2 write_research_brief:被低估的一步

用户的原话是口语的、模糊的、带隐含前提的。直接把对话丢给主管派活,子代理拿到的任务描述会继承这份模糊。

这个节点的作用,是把对话固化成一份书面任务书,后面所有子代理都以它为准。

对应 3.3 说的「任务描述七要素」—— 简报本身写不清楚,七要素就无从谈起。

4.3 compress_research:实现方式很巧妙

# 在子研究员自己的消息历史后面,追加一条「现在切换到压缩模式」的指令
researcher_messages.append(HumanMessage(content=compress_research_simple_human_message))

compression_prompt = compress_research_system_prompt.format(date=get_today_str())
messages = [SystemMessage(content=compression_prompt)] + researcher_messages
response = await synthesizer_model.ainvoke(messages)

注意它不是新开一个 Agent 来读摘要,而是让子研究员自己切换到压缩模式。

差别在哪?

做法压缩者看到的
新开一个 Agent只有子研究员交出来的结果
切换模式(这个实现)全部研究过程,包括试错、被否定的线索

看得越全,压缩质量越高。

还有一个实际的成本优化:压缩用的是单独配置的模型configurable.compression_model)。压缩是个便宜活,没必要用最贵的模型。

外面还包了 3 次重试,专门处理压缩时撞上 token 上限的情况。

4.4 并发上限:超出的不丢弃,而是回一条错误

这段是全文件最值得学的:

allowed  = conduct_research_calls[:configurable.max_concurrent_research_units]
overflow = conduct_research_calls[configurable.max_concurrent_research_units:]

tool_results = await asyncio.gather(*research_tasks)

# 超出的部分,每个都回一条错误消息
for overflow_call in overflow:
ToolMessage(
content=f"Error: Did not run this research as you have already exceeded the maximum "
f"number of concurrent research units. Please try again with "
f"{configurable.max_concurrent_research_units} or fewer research units.",
tool_call_id=overflow_call["id"],
)

主管一口气派了 12 个,系统上限 5 个 —— 前 5 个正常跑,后 7 个各回一条「你派太多了,请用 5 个或更少再试」

这个模式在本专题里已经是第三次出现了

出处场景做法
03并行调了两次清单工具回一条错误消息,让模型改成单次
05并行交接产生非法历史清理掉不属于该分支的调用
这里超过并发上限回一条可操作的错误

在 Agent 编排里,错误处理的第一原则是「让模型能读懂并自己改正」,而不是抛异常。

判断标准:你的错误信息,模型读完知道下一步该怎么做吗?

  • ❌ 「Invalid argument」
  • ✅ 「你派了 12 个但上限是 5 个,请用 5 个或更少再试」

顺带,tool_call_id 必须原样带回 —— 回想 01,少一个 id 整轮消息就非法了。

4.5 两层迭代熔断

# 主管这一层
exceeded_allowed_iterations = research_iterations > configurable.max_researcher_iterations
# 子研究员那一层
exceeded_iterations = state.get("tool_call_iterations", 0) >= configurable.max_react_tool_calls

主管派活的轮数、子研究员的工具调用次数,各有各的上限

对应 3.4 说的双层预算设计 —— 一层管不住

五、关于 OpenAI / Gemini 的 Deep Research

必须说清楚:它们没有源码,只有 API 文档、产品博客和 system card。

所以本篇的做法是:

  • 编排原理用 Anthropic 的公开提示词讲 —— 那是真在线上跑的
  • 代码实现用 open_deep_research 讲 —— MIT 协议,可以跑可以改
  • OpenAI 那个只讲从 API 行为能观察到的部分,不假装拆过它

其他值得参考的开源实现:

项目Star特点
dzhng/deep-research19,571最简实现,想快速理解整个流程先看这个
gpt-researcher29,035出现最早,工程完整度高
smolagents/examples/open_deep_research28,873(主仓)HuggingFace 复刻,跑 GAIA 基准
Hello-Agents 第十四章73,668(主仓)中文,TODO 驱动的三阶段流程,2152 行带 12 张图

六、常见故障与排查

你看到的现象原因怎么修
三个子代理搜了同一批网页派活时没划分边界任务描述七要素的第 7 条(3.3)
一轮接一轮,成本失控没有收敛判断「收益递减就停」+ 双层迭代熔断(3.5 ①、4.5)
主管上下文照样爆子代理结果全量回传加压缩环节(4.3)+ 独立消息通道(4.1)
报告有引用,但对不上原文让写手兼职挂引用独立的引用代理(3.6)
报告内容空泛,像摘要的摘要让子代理写了最终报告主管必须自己写(3.5 ②)
主管一次派 20 个,系统崩了没有并发上限上限 + 溢出回错误(4.4)
所有子代理都跑偏研究简报本身就模糊先固化任务书(4.2)
简单问题也跑了五分钟没有判题型,一律按复杂处理加三分类(3.1)

七、全局定位

Deep Research 在最右端:最贵、最慢、最不可预测,但能干最难的活

什么时候不该用

  • 问题有确定答案 —— 「Python 怎么读 CSV」,一次搜索的事,它会给你一篇三千字报告
  • 信息源封闭且少 —— 只需要查内部两个文档,直接用 RAG
  • 要求快 —— 一次完整的 Deep Research 是分钟级的,不是秒级
  • 成本敏感 —— 一次运行可能是几十次模型调用加上百次网页抓取,单次成本比普通问答高两三个数量级

用它之前先确认:这个问题值不值得花这个钱。

八、小结与检查清单

核心三句话

  1. Deep Research 不是新范式,是前五个的组合,新增的只有压缩引用
  2. 派活的质量决定一切 —— 任务描述七要素,一条都不能少
  3. 预算和熔断必须两层:提示词里写一层,系统强制一层

自查清单

  • 有没有先把用户对话固化成一份书面研究简报?
  • 主管有没有先判题型(深度/广度/直球)再派活?
  • 派几个子代理,有没有依据?还是拍脑袋?
  • 任务描述里,七要素(尤其是范围边界和输出格式)齐全吗?
  • 子代理有工具调用预算吗?系统层有强制上限吗?
  • 子代理回来之前有压缩环节吗?压缩用的是便宜模型吗?
  • 最终报告是主管自己写的,还是派给子代理写的?
  • 引用是单独一个环节,还是让写手顺手挂的?
  • 并发有上限吗?超出的调用是丢弃还是回一条可操作的错误?
  • 有「收益递减就停」的判断吗?

参考资料

资料位置协议
Anthropic 生产提示词原文patterns/agents/prompts/research_lead_agent.md 23KB、research_subagent.md 9.1KB、citations_agent.md 2.9KBMIT
代码骨架open_deep_research/deep_researcher.py 30KB + prompts.py 21KBMIT
工程复盘Anthropic — How we built our multi-agent research system——
中文实战Hello-Agents 第十四章CC BY-NC-SA 4.0

下一篇:07 · 横向对比与选型 —— 六个范式放在一起,到底怎么选。